package com.autonavi.yunda.yunji.core.utils.diling;

import com.autonavi.yunda.yunji.core.engine.exception.EngineException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.PrecisionModel;
import org.locationtech.jts.io.WKTReader;
import org.locationtech.jts.simplify.TopologyPreservingSimplifier;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

@Slf4j
public class AreaUtil {
    public final static String POIID_PATTERN = "^[A-Za-z0-9_\\-]{2,30}$";
    public final static String WPOIID_PATTERN = "^[A-Za-z0-9_\\-]{2,30}$";
    public final static String ADWPOIID_PATTERN = "^[A-Za-z0-9_\\-]{2,30}$";
    public final static String ADPOIID_PATTERN = "^[A-Za-z0-9_\\-]{2,30}$";
    public final static String WOWID_PATTERN = "^[A-Za-z0-9_\\-]{2,30}$";
    public final static String POINT_PATTERN = "^[0-9\\.\\,]{3,50}$";

    /**
     * 检查是否是直辖市，是返回true
     */

    public static Boolean checkZXS(String adcode) {
        if (StringUtils.isEmpty(adcode)) {
            return false;
        }
        ArrayList<String> zxsAdcode = new ArrayList<>();
        zxsAdcode.add("11");//北京
        zxsAdcode.add("12");//天津
        zxsAdcode.add("31");//上海
        zxsAdcode.add("50");//重庆
        zxsAdcode.add("81");//香港
        zxsAdcode.add("82");//澳门
        return zxsAdcode.contains(adcode);
    }

    public static Boolean checkPoiid(String poiid) {
        return !StringUtils.isEmpty(poiid) && poiid.matches(POIID_PATTERN);
    }


    public static Boolean checkPoint(String poiid) {
        return !StringUtils.isEmpty(poiid) && poiid.matches(POINT_PATTERN);
    }


    public static Boolean checkWowid(String wowid) {
        return !StringUtils.isEmpty(wowid) && wowid.matches(WOWID_PATTERN);
    }

    public static Boolean checkWPoiid(String wowid) {
        return !StringUtils.isEmpty(wowid) && wowid.matches(WPOIID_PATTERN);
    }

    public static Boolean checkADWPoiid(String wowid) {
        return !StringUtils.isEmpty(wowid) && wowid.matches(ADWPOIID_PATTERN);
    }

    public static Boolean checkADPoiid(String wowid) {
        return !StringUtils.isEmpty(wowid) && wowid.matches(ADPOIID_PATTERN);
    }


    /**
     * 根据adcode获取分表  geo画像的
     */
    public static String getGeoIndexTbname(String adcode) {
        String tbname = null;
        if (adcode.length() >= 4) {
            HashMap<String, String> adcode4Hash = new HashMap<>();
            adcode4Hash.put("4401", "12");
            adcode4Hash.put("4403", "13");
            adcode4Hash.put("4419", "14");
            adcode4Hash.put("4406", "14");
            adcode4Hash.put("4413", "14");
            adcode4Hash.put("4420", "14");
            String adcode4 = adcode.substring(0, 4);
            if (adcode4Hash.containsKey(adcode4)) {
                return adcode4Hash.get(adcode4);
            }
        }
        if (adcode.length() >= 2) {
            HashMap<String, String> adcode2Hash = new HashMap<>();
            adcode2Hash.put("11", "00");
            adcode2Hash.put("12", "01");
            adcode2Hash.put("14", "01");
            adcode2Hash.put("15", "01");
            adcode2Hash.put("13", "02");
            adcode2Hash.put("31", "04");
            adcode2Hash.put("32", "05");
            adcode2Hash.put("33", "06");
            adcode2Hash.put("35", "07");
            adcode2Hash.put("34", "08");
            adcode2Hash.put("36", "08");
            adcode2Hash.put("37", "09");
            adcode2Hash.put("41", "10");
            adcode2Hash.put("42", "11");
            adcode2Hash.put("43", "11");
            adcode2Hash.put("50", "16");
            adcode2Hash.put("51", "17");
            String adcode2 = adcode.substring(0, 2);
            if (adcode2Hash.containsKey(adcode2)) {
                return adcode2Hash.get(adcode2);
            }
        }

        HashMap<String, String> adcode1Hash = new HashMap<>();
        adcode1Hash.put("2", "03");
        adcode1Hash.put("4", "15");
        adcode1Hash.put("5", "18");
        adcode1Hash.put("6", "19");
        adcode1Hash.put("7", "19");
        adcode1Hash.put("8", "19");
        String adcode1 = adcode.substring(0, 1);
        if (adcode1Hash.containsKey(adcode1)) {
            return adcode1Hash.get(adcode1);
        }

        return tbname;
    }

    //aoi和热力图
    public static String getGeoHotTbname(String adcode) {
        String tbname = null;

        HashMap<String, String> adcode2Hash = new HashMap<>();
        adcode2Hash.put("11", "00");
        adcode2Hash.put("12", "01");
        adcode2Hash.put("13", "01");
        adcode2Hash.put("14", "01");
        adcode2Hash.put("21", "02");
        adcode2Hash.put("22", "02");
        adcode2Hash.put("23", "02");
        adcode2Hash.put("31", "03");
        adcode2Hash.put("32", "04");
        adcode2Hash.put("33", "05");
        adcode2Hash.put("34", "06");
        adcode2Hash.put("35", "06");
        adcode2Hash.put("36", "07");
        adcode2Hash.put("37", "07");
        adcode2Hash.put("41", "08");
        adcode2Hash.put("42", "08");
        adcode2Hash.put("46", "08");
        adcode2Hash.put("46", "08");
        adcode2Hash.put("44", "09");
        adcode2Hash.put("50", "10");
        adcode2Hash.put("61", "10");
        adcode2Hash.put("51", "11");
        adcode2Hash.put("53", "11");
        adcode2Hash.put("15", "12");
        adcode2Hash.put("43", "12");
        adcode2Hash.put("45", "12");
        adcode2Hash.put("52", "12");
        adcode2Hash.put("54", "12");
        adcode2Hash.put("62", "12");
        adcode2Hash.put("63", "12");
        adcode2Hash.put("64", "12");
        adcode2Hash.put("65", "12");
        String adcode2 = adcode.substring(0, 2);
        if (adcode2Hash.containsKey(adcode2)) {
            return adcode2Hash.get(adcode2);
        }

        return tbname;
    }

    public static String getGeoLenByWktV3(String wkt, String genLen) {

        Integer area = getAreas(wkt);
        int geoLenInt = Integer.parseInt(genLen);
        Long area1000 = 20000000000L;
        Long area500 = 314000000L;
        Long area100 = 28260000L;
        Integer area50 = 7065000;
        Integer area10 = 282600;

        if (geoLenInt != 10 && geoLenInt != 50 && geoLenInt != 100 && geoLenInt != 500 && geoLenInt != 1000) {
            throw new EngineException("1003", "geoLen in (10,50,100,500,1000)");
        }

        if (area == null || area < 200 || area > 20000000000L) {
            throw new EngineException("1003", "area is need in(200," + area1000 + ")");
        } else if (geoLenInt == 500 && area > area500) {
            throw new EngineException("1003", "genLen is 500,area is need less " + area500);
        } else if (geoLenInt == 100 && area > area100) {
            throw new EngineException("1003", "genLen is 100,area is need less " + area100);
        } else if (geoLenInt == 50 && area > area50) {
            throw new EngineException("1003", "genLen is 50,area is need less " + area50);
        } else if (geoLenInt == 10 && area > area10) {
            throw new EngineException("1003", "genLen is 10,area is need less " + area10);
        }
        return genLen;
    }

    public static String getGeoLenByWkt(String wkt, Integer version) throws EngineException {

        Integer area = getAreas(wkt);
        Integer geoLen = 1000;
        if (version != null && version == 2) {
            geoLen = getGeoLenByAreaV2(area.longValue());
        } else {
            geoLen = getGeoLenByAreaV1(area.longValue());
        }
        return geoLen.toString();
    }


    public static Integer getGeoLenByAreaV2(Long area) throws EngineException {

        Integer geoLen;//半径
        //2w平方公里
        if (area == null || area < 200 || area > 20000000000L) {
            throw new EngineException("1003", "area is need in(200,20000000000)");
        } else if (area <= 78500000)//
        {
            geoLen = 100;

        } else {
            geoLen = 1000;
        }
        return geoLen;
    }

    public static Integer getGeoLenByAreaV1(Long area) throws EngineException {

        Integer geoLen;//半径
        //2平方公里
        if (area == null || area < 20 || area > 2000000000L) {
            throw new EngineException("1003", "area is need in(20,2000000000)");
        } else if (area <= 785000) {
            //500米以下用10米块
            geoLen = 10;
        } else if (area <= 78500000)//
        {
            //5公里以下用100米块
            geoLen = 100;
        } else {
            geoLen = 1000;
        }
        return geoLen;
    }


    public static Integer getGeoLenByAreaV3(Long area, Integer selGeoLen) throws EngineException {

        int geoLen;//半径
        //2平方公里
        if (area == null || area < 20 || area > 2000000000L) {
            throw new EngineException("1003", "area is need in(20,2000000000)");
        } else if (area <= 785000) {
            //500米以下用10米块
            geoLen = 10;
        } else if (area <= 78500000)//
        {
            //5公里以下用100米块
            geoLen = 100;
        } else {
            geoLen = 1000;
        }
        return geoLen;
    }


    //如果两点的距离小于等于lenght,返回true；否则返回false
    public boolean distancePointCheck(Double x1, Double y1, Double x2, Double y2, Double length) {
        if (length != null && x1 != null && y1 != null && x2 != null && y2 != null) {
            Double dis = distancePoint(x1, y1, x2, y2);
            return dis != -1 && dis <= length;
        }
        return true;
    }

    //算坐标点偏移
    //    double min_x =  squarePoint(x,y,r,"min_x");
    //    double min_y =  squarePoint(x,y,r,"min_y");
    //    double max_x =  squarePoint(x,y,r,"max_x");
    //    double max_y =  squarePoint(x,y,r,"max_y");
    public double squarePoint(double x, double y, double length, String type) {
        Integer cdzc = 40075700;//赤道周长
        Integer zwzc = 40008080;//子午线周长
        double pos = 0;
        if ("min_x".equals(type)) {
            pos = x - length * 360 / (cdzc * Math.cos(2 * Math.PI / 360 * y));
        } else if ("max_x".equals(type)) {
            pos = x + length * 360 / (cdzc * Math.cos(2 * Math.PI / 360 * y));
        } else if ("min_y".equals(type)) {
            pos = y - length * 360 / zwzc;
        } else if ("max_y".equals(type)) {
            pos = y + length * 360 / zwzc;
        }
        pos = (double) Math.round(pos * 100000000) / 100000000;
        log.info("squarePoint: x:" + x + ";y:" + y + ";length:" + length + ";y:" + y + ";type:" + type + ";pos:" + pos);
        return pos;
    }

    public Double distancePoint(Double x, Double y, Double x1, Double y1) {
        double dis = 0;
        if ((x.equals(x1)) && (y.equals(y1))) {
            return dis;
        }
        try {
            double ra = 6378.140;
            // 赤道半径 (km)
            double rb = 6356.755;
            // 极半径 (km)
            double flatten = (ra - rb) / ra;
            //地球扁率

            double radLatA = Math.toRadians(y);
            double radLatB = Math.toRadians(y1);
            double radLngA = Math.toRadians(x);
            double radLngB = Math.toRadians(x1);

            double pA = Math.atan(rb / ra * Math.tan(radLatA));
            double pB = Math.atan(rb / ra * Math.tan(radLatB));


            double xx = Math.acos(
                    Math.sin(pA) * Math.sin(pB) + Math.cos(pA) * Math.cos(pB) * Math.cos(radLngA - radLngB));
            double c1 = (Math.sin(xx) - xx) * Math.pow((Math.sin(pA) + Math.sin(pB)), 2) / Math.pow(Math.cos(xx / 2),
                    2);
            double c2 = (Math.sin(xx) + xx) * Math.pow((Math.sin(pA) - Math.sin(pB)), 2) / Math.pow(Math.sin(xx / 2),
                    2);
            double dr = flatten / 8 * (c1 - c2);
            dis = ra * (xx + dr);
        } catch (Exception e) {
            dis = -1;
            log.warn(
                    "exception distancePoint() x:" + x + ";y:" + y + ";x1:" + x1 + ";y1:" + y1 + ";dis:" + dis + ";" + e
                            .getMessage(), e);
        }

        return dis;
    }

    public static String realWkt(String wkt) {
        String wktReal = "";
        if (!StringUtils.isEmpty(wkt)) {
            String[] wktArr = wkt.split("\\(");
            int len = wktArr.length;

            String wktS1 = wktArr[(len - 1)];
            String[] wktArr1 = wktS1.split("\\)");
            wktReal = wktArr1[0];
        }
        log.info("wkt:" + wkt);
        log.info("wktReal:" + wktReal);
        return wktReal;
    }

    //来自file_aoi_area.py
    public static Integer getAreas(String wkt) throws EngineException {

        try {
            Integer area = 0;

            Double earchWDZC = 40008.08 * 1000;
            String[] pointArray = wkt.split(",");
            Integer pointNum = pointArray.length;
            if (pointNum < 3) {
                return 0;
            }

            Double wd = Double.parseDouble(pointArray[0].split(" ")[1]);
            Double s = 0D;
            s = wd * (Double.parseDouble(pointArray[pointNum - 1].split(" ")[0]) -
                    Double.parseDouble(pointArray[1].split(" ")[0]));
            for (Integer i = 1; i < pointNum; i++) {
                s += Double.parseDouble(pointArray[i].split(" ")[1]) *
                        (Double.parseDouble(pointArray[i - 1].split(" ")[0]) - Double.parseDouble(
                                pointArray[(i + 1) % pointNum].split(" ")[0]));
            }

            s = Math.abs(s) / 2 * Math.cos((2 * Math.PI / 360) * wd) * (earchWDZC / 360) * (earchWDZC / 360);
//            System.out.println(s);
            area = s.intValue();
            log.info("area is " + area);
            return area;
        } catch (Exception e) {
            log.info("wkt" + wkt);
            log.warn("getAreas-Exception:" + e.getMessage(), e);
            throw new EngineException("1003", "wkt getAreas Exception");
        }
    }

    /**
     * 检查wkt格式是否为收尾相同的坐标，至少4个坐标点
     * 该方法使用时考虑是否会影响老的业务方
     */
    public static boolean checkWkt(String wkt) throws EngineException {
        try {
            String[] wktPoint = wkt.split(",");
            if (ObjectUtil.isEmpty(wkt) || wktPoint.length < 4) {
                return false;
            }
            if (wktPoint[wktPoint.length - 1].equals(wktPoint[0])) {
                return true;
            }
            return false;
        } catch (Exception e) {
            throw new EngineException("1003", "format wkt error !");
        }

    }


    public static double getUsePoint(Double point, Integer len, String pointType, String varName)
            throws EngineException {//检查有效坐标

        Double maxVal = 150.0;
        Double minVal = 0.0;
        if (point == null) {
            return point;
        }
        if (pointType != null) {
            if ("sys_point_x".equals(pointType)) {
                maxVal = 150.0;
                minVal = 70.0;
            } else if ("sys_point_y".equals(pointType)) {
                maxVal = 55.0;
                minVal = 0.0;
            } else if ("sys_point_r".equals(pointType)) {
                maxVal = 6.0;
                minVal = 0.25;
            } else if ("sys_point_r_2".equals(pointType)) {
                maxVal = 3.0;
                minVal = 0.1;
            }
        }
        if (point < minVal || point > maxVal) {
            throw new EngineException("1003", varName + " val(" + point + ") error." + minVal + " <= point <= " + maxVal);
        }
        if (len == null) {
            len = 4;
        } else if (len > 6) {
            len = 6;
        }
        double pointR;
        try {
            pointR = ObjectUtil.getUseDouble(point, len);
        } catch (Exception e) {
            throw new EngineException("1003", "format point to double error!" + point);
        }
        return pointR;
    }

    public static String circleWkt(String xy, String radius) throws EngineException {

        String[] xyData = xy.split(",");
        if (xyData.length != 2) {
            throw new EngineException("1003", "xy error!x,y");
        }

        if (StringUtils.isEmpty(xyData[0])) {
            throw new EngineException("1003", "point x empty error!");
        }

        double x = getUsePoint(Double.parseDouble(xyData[0]), 6, "sys_point_x", "x");

        if (StringUtils.isEmpty(xyData[1])) {
            throw new EngineException("1003", "point y empty error!");
        }

        double y = getUsePoint(Double.parseDouble(xyData[1]), 6, "sys_point_y", "y");

        return circleWkt(x, y, Double.parseDouble(radius));
    }

    //根据x,y，半径(公里)获取圆的wkt
    public static String circleWkt(double x, double y, double radius) throws EngineException {
        Integer points = 32;
        ArrayList<String> wkt = new ArrayList<>();
        String wktStr = null;
        try {
            double pi = 3.141592653589793;
            for (Integer i = 0; i < points; i++) {
                float angle = (360 * i / points);
                float dx = (float) (radius * Math.sin(pi / 180 * angle)) / (float) (111 * Math.cos(y * pi / 180));
                double dy = (radius * Math.cos(pi / 180 * angle)) / 111;
                wkt.add(ObjectUtil.getUseDouble((dx + x), 6) + " " + ObjectUtil.getUseDouble((dy + y), 6));
            }
            wkt.add(wkt.get(0));

            wktStr = StringUtils.join(wkt, ",");
        } catch (Exception e) {
            log.warn("circleWkt:" + e.getMessage(), e);
        }
        if (StringUtils.isEmpty(wktStr)) {
            throw new EngineException("1003", "create wkt error!");
        }
        return wktStr;
    }

    /**
     * 获取wkt外围矩形
     */
    public static String funWktExpandRectangle(String wkt) throws EngineException {
        try {
            String[] pointArray = wkt.split(",");
            List<BigDecimal> xList = new ArrayList<BigDecimal>();
            List<BigDecimal> yList = new ArrayList<BigDecimal>();
            for (String poi : pointArray) {
                BigDecimal x = new BigDecimal(poi.split(" ")[0]);
                BigDecimal y = new BigDecimal(poi.split(" ")[1]);
                xList.add(x);
                yList.add(y);
            }
            String minX = getMin(xList).toString();
            String maxX = getMax(xList).toString();
            String minY = getMin(yList).toString();
            String maxY = getMax(yList).toString();
            //顺时针方向围栏，首尾相连
            String expandWkt = minX + " " + maxY + ","
                    + maxX + " " + maxY + ","
                    + maxX + " " + minY + ","
                    + minX + " " + minY + ","
                    + minX + " " + maxY;
            return expandWkt;
        } catch (Exception e) {
            log.warn("## funWktExpandRectangle, wkt:[{}]", wkt, e);
            throw new EngineException("1003", "wkt error");
        }
    }

    public static BigDecimal getMin(List<BigDecimal> list) {
        BigDecimal min = list.get(0);
        for (BigDecimal a : list) {
            if (a.compareTo(min) == -1) {
                min = a;
            }
        }
        return min;
    }

    public static BigDecimal getMax(List<BigDecimal> list) {
        BigDecimal max = list.get(0);
        for (BigDecimal a : list) {
            if (a.compareTo(max) == 1) {
                max = a;
            }
        }
        return max;
    }

    public static void main(String[] args) throws EngineException {

        int c = AreaUtil.getAreas("117.29135895 31.75108337,117.29190826 31.75630379,117.28745622 31.75620651,117.28575906 31.75822449,117.28534369 31.75611298,117.28172865 31.75605583,117.28664761 31.75163078,117.28129092 31.74717123,117.28305817 31.74549484,117.2842865 31.7454834,117.28591102 31.74318737,117.28883362 31.74423565,117.2896347 31.74593925,117.29151154 31.74533844,117.29319763 31.74717331,117.2927475 31.74769974,117.29402161 31.74740982,117.29525757 31.7482357,117.29496566 31.74879198,117.29598236 31.74878693,117.29602051 31.75011253,117.29701025 31.75014023,117.29662323 31.75074005,117.29135895 31.75108337");
        System.out.println(c);
    }


    /**
     * 简化wkt
     */
    public static Geometry simplifyTP(double distance, String wkt) throws Exception {
        GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(PrecisionModel.FLOATING), 4326);
        WKTReader wktReader = new WKTReader(geometryFactory);
        Geometry g;
        if (wkt.contains("MULTIPOLYGON")) {
            g = wktReader.read(wkt);
        } else {
            g = wktReader.read("MULTIPOLYGON (((" + wkt + ")))");
        }

        return TopologyPreservingSimplifier.simplify(g, distance);
    }

    /**
     * 限制wkt数量
     */
    public static String handleWkt(String wkt, int number, int instance) throws Exception {
        double distance = instance > 0 ? (double) instance / 100000 : 0.0005D;
        Geometry geometry = simplifyTP(distance, wkt);
        int pointNumber = geometry.getNumPoints();
        String wktRes = geometry.toString().replaceAll(geometry.getGeometryType().toUpperCase(), "")
                .replaceAll("\\(", "").replaceAll("\\)", "").replaceAll(", *", ",").trim();
        if (pointNumber > number) {
            int index = pointNumber - number;
            BigDecimal step = BigDecimal.valueOf(pointNumber - 2).divide(BigDecimal.valueOf(index), 1, RoundingMode.FLOOR);
            String[] dataArray = wktRes.split(",");
            int count = 1;
            List<String> result = new ArrayList<>();
            int length = dataArray.length;
            for (int i = 0; i < length; i++) {
                if (count <= index) {
                    int removeIndex = step.multiply(BigDecimal.valueOf(count)).setScale(0, RoundingMode.FLOOR).intValue();
                    if (removeIndex == i) {
                        count++;
                        continue;
                    }
                }
                result.add(dataArray[i]);
            }
            wktRes = StringUtils.join(result, ",");
        }
        return wktRes;
    }
}
